﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;

namespace stockUploader.Core
{
    public delegate void FtpRequestCompletedDelegate(object sender, FtpRequestCompletedEventArgs ea);
    public delegate void FtpUploadingDelegate(object sender, FtpUploading ea);
    public delegate void FtpUploadedDelegate(object sender, FtpUploaded ea);

    public class FtpRequestCompletedEventArgs : EventArgs
    {
        public Token token { get; set; }
        public bool Success { get; set; }
        public FtpStatusCode StatusCode { get; set; }
        public FtpWebResponse Response { get; set; }
    }

    public class FtpUploading : EventArgs
    {
        public FileInfo FileInfo { get; set; }
        public long FileSize { get; set; }
        public long Uploaded { get; set; }
        public float Speed { get; set; }
        public bool Cancel { get; set; }
    }

    public class FtpUploaded : EventArgs
    {
        public FileInfo FileInfo { get; set; }
        public bool IsSuccess { get; set; }
    }

    public class FtpStockClient 
    {
        public const int Timeout = 3000; // 3 sec

        private Stock _stock;
        private string _ftp;

        public Stock Stock { get { return _stock; } }

        public event FtpRequestCompletedDelegate RequestCompleted;
        public event FtpUploadingDelegate Uploading;
        public event FtpUploadedDelegate Uploaded;

        protected void OnFtpUploading(FtpUploading e)
        {
            if(Uploading != null)
            {
                Uploading(this, e);
            }
        }

        protected void OnFtpUploaded(FtpUploaded e)
        {
            if(Uploaded != null)
            {
                Uploaded(this, e);
            }
        }

        public FtpStockClient(Stock stock)
        {
            _stock = stock;
            _stock.Ftp = _stock.Ftp.Replace('\\', '/');
            _stock.Folder = _stock.Folder.Replace('\\', '/');
            _ftp = (stock.Ftp.StartsWith("ftp://") ? stock.Ftp : "ftp://" + stock.Ftp).TrimEnd('/');
        }

        private FtpWebRequest getRequest(FileInfo file)
        {
            return getRequest(file.Name);
        }
        private FtpWebRequest getRequest()
        {
            return getRequest(String.Empty);
        }
        private FtpWebRequest getRequest(string file)
        {
            string url = getFileUrl(file);
            var _request = (FtpWebRequest)FtpWebRequest.Create(url);
            _request.Credentials = new NetworkCredential(_stock.Login, _stock.Password);
            _request.UseBinary = true;
            //request.UsePassive = stock.PassiveMode.HasValue && stock.PassiveMode.Value;
            return _request;
        }

        private string getFileUrl(string file)
        {
            string path = (_stock.Folder ?? String.Empty).Trim('/') + "/" + file;
            path = path.StartsWith("/") ? path : "/" + path;
            string url = _ftp + path;
            return url;
        }

        public void UploadFile(FileInfo file)
        {
            long ftpSize = GetFileSize(file);
            //Console.WriteLine(String.Format("\n\r{0} - {1}", file.Name, ftpSize));
            if(ftpSize < 0)
            {
                // file does not exist on ftp, upload
                upload(file);
            }
            else
            {
                if(ftpSize < file.Length)
                {
                    // file exists on ftp, continue uploading
                    continueUpload(file, ftpSize);
                }
            }
            //Console.WriteLine(String.Format("{0} - {1}", file.Name, size));
        }

        public long GetFileSize(FileInfo file)
        {
            var request = getRequest(file);
            request.Method = WebRequestMethods.Ftp.GetFileSize;
            try
            {
                FtpWebResponse response = (FtpWebResponse)request.GetResponse();
                return response.ContentLength;
            }
            catch(WebException ex)
            {
                if(ex.Response is FtpWebResponse)
                {
                    FtpWebResponse r = (FtpWebResponse)ex.Response;
                    switch(r.StatusCode)
                    {
                        case FtpStatusCode.ActionNotTakenFileUnavailable:
                            if(r.StatusDescription.StartsWith("550 SIZE:"))
                            {
                                //try to get file size from dir list
                                var req = getRequest();
                                req.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
                                try
                                {
                                    FtpWebResponse resp = (FtpWebResponse)req.GetResponse();
                                    string list;
                                    using(StreamReader sr = new StreamReader(resp.GetResponseStream()))
                                    {
                                        list = sr.ReadToEnd();
                                    }
                                    Regex re = new Regex(@" ([0-9]{4,}) [^\n\r]+ " + file.Name);
                                    Match match = re.Match(list);
                                    if(match.Success)
                                    {
                                        return Int64.Parse(match.Groups[1].Value);
                                    }
                                }
                                catch
                                {
                                }
                            }
                            return -1L;
                    }
                }
                throw ex;
            }
            return -99L;
        }

        private void upload(FileInfo file)
        {
            var request = getRequest(file);

            request.Method = WebRequestMethods.Ftp.UploadFile;
            using(FileStream fs = file.OpenRead())
            {
                var stream = request.GetRequestStream();
                sendStream(file, fs, stream);
            }

            getUploadResponse(file, request);

        }

        private void continueUpload(FileInfo file, long ftpSize)
        {
            if(ftpSize < 0) 
                return;

            var request = getRequest(file);

            request.Method = WebRequestMethods.Ftp.AppendFile;
            using(FileStream fs = file.OpenRead())
            {
                fs.Seek(ftpSize, SeekOrigin.Begin);
                var stream = request.GetRequestStream();
                sendStream(file, fs, stream);
            }

            getUploadResponse(file, request);
        }


        private void getUploadResponse(FileInfo file, FtpWebRequest request)
        {
            bool success = false;
            try
            {
                IAsyncResult result = request.BeginGetResponse(null, null);
                if(!result.AsyncWaitHandle.WaitOne(Timeout) || !result.IsCompleted)
                {
                    request.Abort();
                }
                else
                {
                    var response = (FtpWebResponse)request.EndGetResponse(result);
                    success = true;
                    OnFtpUploaded(new FtpUploaded { FileInfo = file });
                    //Console.WriteLine(String.Format("{0} - {1}", file.Name, response.StatusCode));
                }
            }
            catch { }

            if(!success)
            {
                // timeout occured, check the file size
                long ftpSize = GetFileSize(file);
                if(ftpSize < file.Length)
                    continueUpload(file, ftpSize);
                else 
                    OnFtpUploaded(new FtpUploaded { FileInfo = file, IsSuccess = ftpSize == file.Length });
            }
        }

        private void sendStream(FileInfo file, FileStream fs, Stream stream)
        {
            const int bufferSize = 4096;
            byte[] buf = new byte[bufferSize];
            int read;
            DateTime started;
            while((read = fs.Read(buf, 0, buf.Length)) > 0)
            {
                started = DateTime.Now;
                stream.Write(buf, 0, read);
                TimeSpan span = DateTime.Now - started;
                var e = new FtpUploading
                {
                    Cancel = false,
                    FileInfo = file,
                    FileSize = fs.Length,
                    Uploaded = fs.Position,
                    Speed = span.Milliseconds != 0 ? bufferSize / span.Milliseconds * 1000 : -1
                };
                OnFtpUploading(e);
                if(e.Cancel)
                    break;
                //Console.Write(String.Format("\r{0} - {1} / {2}", file.Name, fs.Position, fs.Length));
            }
        }


        //private void callback(IAsyncResult result)
        //{
        //    Token token = (Token)result.AsyncState;
        //    if(result.IsCompleted)
        //    {
        //        _processing = true;
        //        lock(_slotMachine)
        //        {
        //            _slotMachine.FreeSlot(token.Slot);
        //        }

        //        try
        //        {
        //            FtpWebResponse response = (FtpWebResponse)token.Request.EndGetResponse(result);
        //            processResponse(response, token);
        //        }
        //        catch(WebException webex)
        //        {
        //            if(webex.Response is FtpWebResponse)
        //            {
        //                processResponse(webex.Response as FtpWebResponse, token);
        //            }
        //        }
        //        catch(Exception ex) { }
        //        _processing = false;
        //    }
        //}

}
}
